package cn.apotato.modules.redis.config;

import cn.apotato.common.core.utils.StringUtils;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.Assert;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.Duration;
import java.util.*;


/**
 * 缓存配置
 *
 * @author 胡晓鹏
 * @date 2023/03/17
 */
@Configuration
@EnableCaching
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfig extends CachingConfigurerSupport {

    public static final int REDIS_DB = 10;

    @Resource
    private RedisProperties redisProperties;

    @Bean("redisTemplate")
    @Primary
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }

    @Bean("queueRedisTemplate")
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate() {
        RedisTemplate<Object, Object> templateByDb = getRedisTemplate(REDIS_DB);
        return serializer(templateByDb);
    }


    /**
     * 获取redisTemplate实例
     *
     * @param db
     * @return
     */
    public RedisTemplate<Object, Object> getRedisTemplate(int db) {
        final RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        LettuceConnectionFactory factory = (LettuceConnectionFactory) factory();
        factory.setDatabase(db);
        redisTemplate.setConnectionFactory(factory);
        return serializer(redisTemplate);
    }

    /**
     * redis单机配置
     *
     * @return
     */
    private RedisStandaloneConfiguration redisConfiguration() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(redisProperties.getHost());
        redisStandaloneConfiguration.setPort(redisProperties.getPort());
        //设置密码
        if (redisProperties.getPassword() != null) {
            redisStandaloneConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword()));
        }
        return redisStandaloneConfiguration;
    }

    /**
     * redis哨兵配置
     *
     * @return
     */
    private RedisSentinelConfiguration getSentinelConfiguration() {
        RedisProperties.Sentinel sentinel = redisProperties.getSentinel();
        if (sentinel != null) {
            RedisSentinelConfiguration config = new RedisSentinelConfiguration();
            config.setMaster(sentinel.getMaster());
            if (!StringUtils.isEmpty(redisProperties.getPassword())) {
                config.setPassword(RedisPassword.of(redisProperties.getPassword()));
            }
            config.setSentinels(createSentinels(sentinel));
            return config;
        }
        return null;
    }

    /**
     * 获取哨兵节点
     *
     * @param sentinel
     * @return
     */
    private List<RedisNode> createSentinels(RedisProperties.Sentinel sentinel) {
        List<RedisNode> nodes = new ArrayList<>();
        for (String node : sentinel.getNodes()) {
            String[] parts = StringUtils.split(node, ":");
            Assert.state(parts.length == 2, "redis哨兵地址配置不合法！");
            nodes.add(new RedisNode(parts[0], Integer.parseInt(parts[1])));
        }
        return nodes;
    }

    /**
     * redis集群配置
     *
     * @return
     */
    private RedisClusterConfiguration getRedisClusterConfiguration() {
        RedisProperties.Cluster cluster = redisProperties.getCluster();
        if (cluster != null) {
            RedisClusterConfiguration config = new RedisClusterConfiguration();
            config.setClusterNodes(createCluster(cluster));
            if (!StringUtils.isEmpty(redisProperties.getPassword())) {
                config.setPassword(RedisPassword.of(redisProperties.getPassword()));
            }
            config.setMaxRedirects(redisProperties.getCluster().getMaxRedirects());
            return config;
        }
        return null;
    }

    /**
     * 获取集群节点
     *
     * @param cluster
     * @return
     */
    private List<RedisNode> createCluster(RedisProperties.Cluster cluster) {
        List<RedisNode> nodes = new ArrayList<>();
        for (String node : cluster.getNodes()) {
            String[] parts = StringUtils.split(node, ":");
            Assert.state(parts.length == 2, "redis哨兵地址配置不合法！");
            nodes.add(new RedisNode(parts[0], Integer.parseInt(parts[1])));
        }
        return nodes;
    }

    /**
     * 连接池配置
     *
     * @return
     */
    private GenericObjectPoolConfig redisPool() {
        GenericObjectPoolConfig genericObjectPoolConfig =
                new GenericObjectPoolConfig();
        genericObjectPoolConfig.setMaxIdle(redisProperties.getLettuce().getPool().getMaxIdle());
        genericObjectPoolConfig.setMinIdle(redisProperties.getLettuce().getPool().getMinIdle());
        genericObjectPoolConfig.setMaxTotal(redisProperties.getLettuce().getPool().getMaxActive());
        genericObjectPoolConfig.setTestOnBorrow(true);
        genericObjectPoolConfig.setTestWhileIdle(true);
        genericObjectPoolConfig.setTestOnReturn(false);
        genericObjectPoolConfig.setEvictorShutdownTimeout(redisProperties.getTimeout());
        genericObjectPoolConfig.setMaxWait(Duration.ofMillis(5000));
        return genericObjectPoolConfig;
    }

    /**
     * redis客户端配置
     *
     * @return
     */
    private LettuceClientConfiguration clientConfiguration() {
        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder();
        builder.commandTimeout(redisProperties.getLettuce().getShutdownTimeout());
        builder.shutdownTimeout(redisProperties.getLettuce().getShutdownTimeout());
        builder.poolConfig(redisPool());
        return builder.build();
    }

    /**
     * redis获取连接工厂
     *
     * @return
     */
    @Bean
    @Scope(scopeName = "prototype")
    protected RedisConnectionFactory factory() {
        //根据配置和客户端配置创建连接
        LettuceConnectionFactory lettuceConnectionFactory;
        if (redisProperties.getSentinel() == null && redisProperties.getCluster() == null) {
            //单机模式
            lettuceConnectionFactory = new LettuceConnectionFactory(redisConfiguration(), clientConfiguration());
            lettuceConnectionFactory.afterPropertiesSet();
        } else if (redisProperties.getCluster() == null) {
            //哨兵模式
            lettuceConnectionFactory = new LettuceConnectionFactory(Objects.requireNonNull(getSentinelConfiguration()), clientConfiguration());
            lettuceConnectionFactory.afterPropertiesSet();
        } else {
            //集群模式
            lettuceConnectionFactory = new LettuceConnectionFactory(Objects.requireNonNull(getRedisClusterConfiguration()), clientConfiguration());
            lettuceConnectionFactory.afterPropertiesSet();
        }
        return lettuceConnectionFactory;
    }


    /**
     * 序列化
     *
     * @param redisTemplate
     * @return
     */
    private RedisTemplate<Object, Object> serializer(RedisTemplate<Object, Object> redisTemplate) {
        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(serializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
